package com.connector.base.server;

import com.connector.base.codec.PacketDecoder;
import com.connector.base.codec.PacketEncoder;
import com.connector.base.exception.ClientException;
import com.connector.base.service.BaseService;
import com.connector.base.service.IClient;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.*;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.epoll.EpollSocketChannel;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.concurrent.DefaultThreadFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.channels.spi.SelectorProvider;
import java.util.concurrent.ThreadFactory;

public abstract class NettyTcpClient extends BaseService implements IClient
{
    private static final Logger log;
    public static final String WORKER_THREAD_NAME = "connector_client_worker";
    private static final boolean DEFAULT_USE_EPOLL = false;
    private Bootstrap bootstrap;
    private EventLoopGroup workerGroup;
    private boolean useEpoll;

    public NettyTcpClient() {
        this.useEpoll = false;
    }

    @Override
    public void init() {
        if (this.started.get()) {
            throw new ClientException("server already started");
        }
    }

    @Override
    public void start() {
        if (this.started.compareAndSet(false, true)) {
            if (this.useEpoll) {
                this.createEpollClient();
            }
            else {
                this.createNioClient();
            }
            return;
        }
        throw new ClientException("client stated failure");
    }

    @Override
    public void stop() {
        NettyTcpClient.log.info("try stop {}", (Object)this.getClass().getSimpleName());
        if (this.started.compareAndSet(true, false)) {
            if (this.workerGroup != null) {
                this.workerGroup.shutdownGracefully().syncUninterruptibly();
            }
            NettyTcpClient.log.info("{} stopped success", (Object)this.getClass().getSimpleName());
            return;
        }
        throw new ClientException("client stopped failure");
    }

    public ChannelFuture connect(final String host, final int port) {
        return this.bootstrap.connect((SocketAddress)new InetSocketAddress(host, port)).addListener(f -> {
            if (f.isSuccess()) {
                NettyTcpClient.log.info("start netty client success, host={}, port={}", (Object)host, (Object)port);
            }
            else {
                NettyTcpClient.log.error("start netty client failure, host={}, port={}", new Object[] { host, port, f.cause() });
            }
        });
    }

    private void createNioClient() {
        final NioEventLoopGroup workerGroup = new NioEventLoopGroup(this.getWorkThreadNum(), (ThreadFactory)new DefaultThreadFactory("connector_client_worker"), this.getSelectorProvider());
        workerGroup.setIoRatio(this.getIoRate());
        this.createClient((EventLoopGroup)workerGroup, this.getChannelFactory());
    }

    private void createEpollClient() {
        final EpollEventLoopGroup workerGroup = new EpollEventLoopGroup(this.getWorkThreadNum(), (ThreadFactory)new DefaultThreadFactory("connector_client_worker"));
        workerGroup.setIoRatio(this.getIoRate());
        this.createClient((EventLoopGroup)workerGroup, (ChannelFactory<? extends Channel>)EpollSocketChannel::new);
    }

    private void createClient(final EventLoopGroup workerGroup, final ChannelFactory<? extends Channel> channelFactory) {
        this.workerGroup = workerGroup;
        this.bootstrap = new Bootstrap();
        ((Bootstrap)((Bootstrap)((Bootstrap)this.bootstrap.group(workerGroup)).option(ChannelOption.SO_REUSEADDR,  true)).option(ChannelOption.ALLOCATOR,  PooledByteBufAllocator.DEFAULT)).channelFactory((ChannelFactory)channelFactory);
        this.bootstrap.handler((ChannelHandler)new ChannelInitializer<Channel>() {
            protected void initChannel(final Channel ch) throws Exception {
                NettyTcpClient.this.initPipeline(ch.pipeline());
            }
        });
        this.initOptions(this.bootstrap);
    }

    protected ChannelHandler getDecoder() {
        return (ChannelHandler)new PacketDecoder();
    }

    protected ChannelHandler getEncoder() {
        return (ChannelHandler)new PacketEncoder();
    }

    public abstract ChannelHandler getChannelHandler();

    protected void initPipeline(final ChannelPipeline pipeline) {
        pipeline.addLast("decoder", this.getDecoder());
        pipeline.addLast("encoder", this.getEncoder());
        pipeline.addLast("handler", this.getChannelHandler());
    }

    protected void initOptions(final Bootstrap b) {
        b.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 4000);
        b.option(ChannelOption.TCP_NODELAY, true);
    }

    protected int getWorkThreadNum() {
        return 1;
    }

    public SelectorProvider getSelectorProvider() {
        return SelectorProvider.provider();
    }

    protected int getIoRate() {
        return 50;
    }

    public ChannelFactory<? extends Channel> getChannelFactory() {
        return (ChannelFactory<? extends Channel>)NioSocketChannel::new;
    }

    public Bootstrap getBootstrap() {
        return this.bootstrap;
    }

    public EventLoopGroup getWorkerGroup() {
        return this.workerGroup;
    }

    public boolean isUseEpoll() {
        return this.useEpoll;
    }

    static {
        log = LoggerFactory.getLogger((Class) NettyTcpClient.class);
    }
}
